package pl.edu.agh.intobl;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import pl.edu.agh.intobl.exceptions.IntOblException;

/**
 * Class representing one route in a routeSet - legitimate problem solution
 */
public class Route {

	private LinkedList<Demand> demands = new LinkedList<Demand>();

	public LinkedList<Demand> getDemands() {
		return demands;
	}

	public Integer capacity() {
		int capacity = 0;
		for (Demand demand : demands)
			capacity += demand.load;

		return capacity;
	}

	public boolean addDemander(int demanderNumber, int demanderLoad, int demanderServiceTime, int demanderReadyTime,
			int demanderDueDate) {
		if (capacity() + demanderLoad <= ProblemSolver.vehiclesCapacity) {
			demands.add(new Demand(demanderNumber, demanderLoad, demanderServiceTime, demanderReadyTime,
					demanderDueDate));
			return true;
		}

		return false;
	}

	public boolean addDemander(Demand demand) {
		if (capacity() + demand.load <= ProblemSolver.vehiclesCapacity) {
			demands.add(demand);
			return true;
		}

		return false;
	}

	public int size() {
		return demands.size();
	}

	public Route getSubRoute(int subRouteStartIndex, int subRouteSize) {
		Route subRoute = new Route();
		for (int i = 0; i < subRouteSize; i++) {
			Demand demand = demands.get(subRouteStartIndex + i);
			subRoute.addDemander(demand.demander, demand.load, demand.serviceTime, demand.readyTime, demand.dueDate);
		}

		return subRoute;
	}

	public Route removeSubRoute(int subRouteStartIndex, int subRouteSize) {
		Route subRoute = new Route();
		for (int i = 0; i < subRouteSize; i++) {
			Demand demand = demands.remove(subRouteStartIndex + i);
			subRoute.addDemander(demand.demander, demand.load, demand.serviceTime, demand.readyTime, demand.dueDate);
		}

		return subRoute;
	}

	public int findClosestDemandNotBelongingToRoute() {
		List<Integer> demandsNr = new LinkedList<Integer>();
		for (Demand demand : demands)
			demandsNr.add(demand.demander);

		Collections.sort(demandsNr);

		int closest = 1;
		while (demandsNr.contains(closest))
			closest++;
		double distance = ProblemSolver.distanceMatrix[demands.get(0).demander][closest];

		for (int i = 1; i < ProblemSolver.distanceMatrix[demands.get(0).demander].length; i++) {
			if (demandsNr.contains(i))
				continue;

			if (ProblemSolver.distanceMatrix[demands.get(0).demander][i] < distance) {
				distance = ProblemSolver.distanceMatrix[demands.get(0).demander][i];
				closest = i;
			}
		}
		return closest;
	}

	public Route diff(Route subRoute) {
		Route diffRoute = new Route();
		for (int i = 0; i < demands.size(); i++) {
			Demand demand = demands.get(i);
			if (!subRoute.contains(demand.demander))
				diffRoute.addDemander(demand.demander, demand.load, demand.serviceTime, demand.readyTime,
						demand.dueDate);
		}

		return diffRoute;
	}

	boolean contains(Integer demander) {
		for (Demand demand : demands)
			if (demand.demander == demander)
				return true;

		return false;
	}

	int indexOfDemander(int demanderNr) {
		for (Demand demand : demands)
			if (demand.demander == demanderNr)
				return demands.indexOf(demand);

		return -1;
	}

	public void insertAfter(int demanderNr, Route subRoute) throws IntOblException {
		int index = indexOfDemander(demanderNr);
		if (index == -1)
			throw new IntOblException("Fatal error");// should never happen

		demands.addAll(index + 1, subRoute.demands);
	}

	@Override
	public Object clone() {
		Route clonedRoute = new Route();
		for (int i = 0; i < demands.size(); i++) {
			Demand demand = demands.get(i);
			clonedRoute.addDemander(demand.demander, demand.load, demand.serviceTime, demand.readyTime, demand.dueDate);
		}

		return clonedRoute;
	}

	public void swapDemand(Random rand) {
		int firstIndex = rand.nextInt(demands.size());
		int secondIndex = rand.nextInt(demands.size());

		Collections.swap(demands, firstIndex, secondIndex);
	}

	public void inverseRandomSubRoute(Random rand) {
		int subRouteSize = rand.nextInt(demands.size() - 1) + 1;
		int index = rand.nextInt(demands.size() - subRouteSize);

		Collections.reverse(demands.subList(index, index + subRouteSize));
	}

	public Demand removeRandomDemand(Random rand) {
		return demands.remove(rand.nextInt(demands.size()));
	}

	public void insertAfterRandom(Demand demand, Random rand) {
		demands.add(rand.nextInt(demands.size()), demand);
	}

	public void insertAfterRandom(Route subRoute, Random rand) {
		demands.addAll(rand.nextInt(demands.size()), subRoute.demands);
	}

	@Override
	public String toString() {
		return capacity() + ": " + demands.toString();
	}

	public Route cutTail() {
		Route tailRoute = new Route();
		while (capacity() > ProblemSolver.vehiclesCapacity) {
			tailRoute.demands.addFirst(demands.removeLast());
		}
		return tailRoute.size() == 0 ? null : tailRoute;
	}

	public Route cutTail(int position) {
		Route tailRoute = new Route();
		int tailSize = demands.size() - position;
		for (int i = 0; i < tailSize; i++) {
			tailRoute.demands.addFirst(demands.removeLast());
		}
		return tailRoute.size() == 0 ? null : tailRoute;
	}

	public double length() {
		double length = ProblemSolver.distanceMatrix[0][demands.getFirst().demander];
		for (int i = 0; i < demands.size() - 1; i++)
			length += ProblemSolver.distanceMatrix[demands.get(i).demander][demands.get(i + 1).demander];

		length += ProblemSolver.distanceMatrix[demands.getLast().demander][0];
		return length;
	}

}
